<?php
/**
 * 配置和启动Tcp服务
 */
declare(strict_types=1);

use Swoole\Server;
use Swoole\Server\Task;
use Swoole\Timer;
use Swoole\Constant;
use Libraries\Dispatcher;
use Libraries\Helper;
use Libraries\Config;
use Libraries\File;
use Libraries\Connections\PDOConn;
use Libraries\Connections\RedisConn;
use Libraries\Connections\TcpConn;
use Libraries\Connections\AmqpConn;
use Apps\Tcp\Services\Init;

/**
 * 避免重复启动
 */
if (!defined('APP_PATH')) {
    error_reporting(E_ALL);
    ini_set('display_errors', 'On');
    ini_set('memory_limit', '32M');
    date_default_timezone_set('Asia/Shanghai');
    define('APP_PATH', __DIR__); // 项目路径
    define('APP_NAME', basename(APP_PATH)); // 项目名，与目录名相同
    define('CACHES_PATH', APP_PATH . '/caches'); //缓存目录
    define('SRC_ROOT', APP_PATH . '/../..'); // 源码根目录
    $intDefaultPort = 8081;
    $strDefaultUser = 'www-data';
    require SRC_ROOT . '/inc.php'; // 引入包含文件
} else {
    exit();
}

/**
 * 端口和用户组
 */
$intPort = intval($argv[1] ?? $intDefaultPort);
$intPort || $intPort = $intDefaultPort;
$strUser = trim($argv[2] ?? $strDefaultUser);
if ($strUser == 'root' || $strUser == '') {
    $strUser = $strDefaultUser;
}

/**
 * 不同环境的Server配置
 */
$arrEnvServerSet = Config::get('tcp_server');
$arrEnvServerSet || $arrEnvServerSet = [];

/**
 * 绑定的Host
 */
$strBindHost = '::'; // 默认情况，监听所有的请求
if ($arrEnvServerSet && isset($arrEnvServerSet['bind_host'])) {
    $strBindHost = $arrEnvServerSet['bind_host'];
    unset($arrEnvServerSet['bind_host']);
}

/**
 * Tcp服务实例
 */
$objTcpServer = new Server($strBindHost, $intPort, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);

/**
 * 服务设置
 */
$objTcpServer->set(array_merge($arrEnvServerSet, [
    Constant::OPTION_LOG_FILE => CACHES_PATH . '/swoole/' . $intPort . '.log', // 指定 Swoole 错误日志文件
    Constant::OPTION_LOG_ROTATION => SWOOLE_LOG_ROTATION_DAILY, // 来按日期生成日志
    Constant::OPTION_TCP_DEFER_ACCEPT => 5, // 启用 tcp_defer_accept 特性【默认值：false 关闭】
    Constant::OPTION_USER => $strUser, // 设置 Worker/TaskWorker 子进程的所属用户
    Constant::OPTION_GROUP => $strUser, // 设置 worker/task 子进程的进程用户组
    Constant::OPTION_PID_FILE => CACHES_PATH . '/swoole/' . $intPort . '.pid', // 设置 pid 文件地址。【默认值：无】
    Constant::OPTION_RELOAD_ASYNC => true, // 设置异步重启开关。【默认值：true】
    Constant::OPTION_TCP_FASTOPEN => true, // 开启 TCP 快速握手特性。【默认值：false】
    Constant::OPTION_DISPATCH_MODE => 1, // 收到会轮循分配给每一个 Worker 进程
    Constant::OPTION_MAX_WAIT_TIME => 5, // Worker 进程收到停止服务通知后最大等待时间
    Constant::OPTION_ENABLE_COROUTINE => true, // 开启异步风格服务器的协程支持
    Constant::OPTION_HOOK_FLAGS => SWOOLE_HOOK_ALL, // 一键协程化
    Constant::OPTION_TASK_ENABLE_COROUTINE => true, // 开启后自动在 onTask 回调中创建协程和协程容器，PHP 代码可以直接使用协程 API。
    Constant::OPTION_OPEN_LENGTH_CHECK => true,
    Constant::OPTION_PACKAGE_LENGTH_TYPE => 'n',
    Constant::OPTION_PACKAGE_MAX_LENGTH => 65533, // 约64K
    Constant::OPTION_PACKAGE_LENGTH_OFFSET => 0,
    Constant::OPTION_PACKAGE_BODY_OFFSET => 2,
    // more...
]));
unset($intPort, $strUser, $intDefaultPort, $strDefaultUser, $arrEnvServerSet, $strBindHost); // 回收多余变量

/**
 * 主进程启动时，对进程命名
 */
$objTcpServer->on(Constant::EVENT_START, function (Server $objTcpServer) {
    swoole_set_process_name(APP_NAME . 'Master:' . $objTcpServer->port);
});

/**
 * 管理进程启动时，对进程命名
 */
$objTcpServer->on(Constant::EVENT_MANAGER_START, function (Server $objTcpServer) {
    swoole_set_process_name(APP_NAME . 'Manager:' . $objTcpServer->port);
});

/**
 * Worker进程启动
 */
$objTcpServer->on(Constant::EVENT_WORKER_START, function (Server $objTcpServer, int $intWorkerId) {
    opcache_reset(); // 清除机器码缓存
    gc_disable(); // 禁用内存回收
    swoole_set_process_name(APP_NAME . ($objTcpServer->taskworker ? 'Task' : 'Worker') . '#' . $intWorkerId);
    require SRC_ROOT . '/inc.php'; // 引入包含文件
    /* 进程常驻数据 */
    $objTcpServer->strDataFile = CACHES_PATH . '/data/worker_' . $intWorkerId . '.php';
    $objTcpServer->arrData = (file_exists($objTcpServer->strDataFile) ? include($objTcpServer->strDataFile) : []);
    is_array($objTcpServer->arrData) || $objTcpServer->arrData = [];
    Init::go($intWorkerId);
});

/**
 * 处理请求
 */
$objTcpServer->on(Constant::EVENT_RECEIVE, function (Server $objTcpServer, int $intFd, int $intReactorId, string $strData) {
    try {
        Dispatcher::handleTcpRecv(Helper::unpackTcpMsg($strData), $intFd, $intReactorId);
    } catch (Throwable $objE) { // 记录异常
        Helper::saveThrowLog($objE, 'tcp_recv');
        $objTcpServer->send($intFd, Helper::packTcpMsg(['err_msg' => $objE->getMessage()]));
    }
});

/**
 * 收到其它进程发来的消息
 */
$objTcpServer->on(Constant::EVENT_PIPE_MESSAGE, function (Server $objTcpServer, int $intSrcWorkerId, array $arrData) {
    // ...
});

/**
 * Task任务
 */
$objTcpServer->on(Constant::EVENT_TASK, function (Server $objTcpServer, Task $objTask) {
    try {
        Dispatcher::handleTask($objTask);
    } catch (Throwable $objE) { // 记录异常
        Helper::saveThrowLog($objE, 'task');
        unset($objE);
    }
});

/**
 * Task任务完成
 */
$objTcpServer->on(Constant::EVENT_FINISH, function (Server $objTcpServer, int $intTaskId, array $arrData) {
    try {
        Dispatcher::handleTaskFinish($arrData, $intTaskId);
    } catch (Throwable $objE) { // 记录异常
        Helper::saveThrowLog($objE, 'task_finish');
        unset($objE);
    }
});

/**
 * 进程结束
 */
$objTcpServer->on(Constant::EVENT_WORKER_STOP, function (Server $objTcpServer, int $intWorkerId) {
    /* 清除所有定时器 */
    Timer::clearAll();
    File::writePhpData($objTcpServer->strDataFile, $objTcpServer->arrData, false);
    /* 关闭连接池 */
    PDOConn::close();
    RedisConn::close();
    TcpConn::close();
    AmqpConn::close();
});

/**
 * 服务运行
 */
$objTcpServer->start();
